﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;
using System.Diagnostics;
using jtifedit2.TIF;
using FreeImageAPI;

namespace jtifedit2 {
    public partial class JForm : Form {
        String fpWantOpen = null;

        public JForm(String fp) {
            fpWantOpen = fp;

            InitializeComponent();

            Disposed += new EventHandler(JForm_Disposed);
        }

        void JForm_Disposed(object sender, EventArgs e) {
            CloseTIFF();
        }

        private void bOpen_Click(object sender, EventArgs e) {
            if (!QuerySave())
                return;
            if (ofdtif.ShowDialog(this) == DialogResult.OK) {
                LoadTIFF(ofdtif.FileName);
            }
        }

        private void UpdateTitle() {
            this.Text = title.Replace("[*]", DocName + (isReadOnly ? " [読み取り専用]" : ""));
        }

        String fpopen = null;

        PICS pic = null;

        int cntPG { get { return flpth.Controls.Count; } }

        bool isReadOnly = false;

        Bitmap CreateThumb(PIC1 pic) {
            return pic.CreateThumb(sizeThumb);
        }

        //Rectangle rcth = Fitrect.Fit(new Rectangle(Point.Empty, sizeThumb), pic.Size);
        //Bitmap picth = new Bitmap(sizeThumb.Width, sizeThumb.Height);
        //using (Graphics cv = Graphics.FromImage(picth)) {
        //    cv.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        //    cv.DrawImage(pic, rcth);
        //}
        //return picth;

        String GetPGText(int i) {
            return String.Format("{0}頁", 1 + i);
        }

        enum AllowedAccess {
            None, ReadOnly, ReadWrite,
        }

        AllowedAccess Testaa(String fp) {
            if (0 != (File.GetAttributes(fp) & FileAttributes.ReadOnly))
                return AllowedAccess.ReadOnly;
            try {
                using (File.Open(fp, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { }
                return AllowedAccess.ReadWrite;
            }
            catch (Exception) {
                try {
                    using (File.Open(fp, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { }
                    return AllowedAccess.ReadOnly;
                }
                catch (Exception) {
                    return AllowedAccess.None;
                }
            }
        }

        private void LoadTIFF(String fp) {
            LoadTIFF(fp, fp);
        }

        private void LoadTIFF(String fpLoadFrom, String fpSavePreserved) {
            CloseTIFF();

            AllowedAccess aa = Testaa(fpSavePreserved);

            if (new FileInfo(fpLoadFrom).Length >= 1) {
                using (WIP wip = WIP.Show(this, "開いています...")) {
                    pic = new PICS(fpLoadFrom);
                    int cx = pic.FrameCount;

                    flpth.Controls.Clear();
                    for (int x = 0; x < cx; x++) {
                        ThumbCtl tc = new ThumbCtl();
                        tc.PGImage = pic.GetPage(x);
                        tc.PGCopyOnWrite = true;
                        tc.Thumbnail = CreateThumb(tc.PGImage);
                        tc.Padding = new Padding(10);
                        tc.Text = GetPGText(x);
                        tc.MouseDown += new MouseEventHandler(tc_MouseDown);
                        tc.MouseMove += new MouseEventHandler(tc_MouseMove);
                        tc.MouseUp += new MouseEventHandler(tc_MouseUp);
                        tc.Name = "C" + x;
                        flpth.Controls.Add(tc);
                        if (x == 0) SelPG(0);
                    }
                }
            }

            fpopen = fpSavePreserved;
            isReadOnly = aa != AllowedAccess.ReadWrite;
            UpdateTitle();
        }

        private void AddPG(FIBITMAP newpic) {
            int x = flpth.Controls.Count;
            ThumbCtl tc = new ThumbCtl();
            tc.PGImage = pic.AddPage(newpic);
            tc.PGCopyOnWrite = false;
            tc.Thumbnail = CreateThumb(tc.PGImage);
            tc.Padding = new Padding(10);
            tc.Text = GetPGText(x);
            tc.MouseDown += new MouseEventHandler(tc_MouseDown);
            tc.MouseMove += new MouseEventHandler(tc_MouseMove);
            tc.MouseUp += new MouseEventHandler(tc_MouseUp);
            tc.Name = "C" + x;
            flpth.Controls.Add(tc);
        }

        void tc_MouseUp(object sender, MouseEventArgs e) {
            if (e.Button == MouseButtons.Left) {
                Cursor = Cursors.Default;

                for (int x = 0; x < cntPG; x++) {
                    GetThc(x).Text = GetPGText(x);

                    // pages reordered, then update PGi.
                    if (GetThc(x).Selected) PGi = x;
                }
            }
        }

        void tc_MouseMove(object sender, MouseEventArgs e) {
            if (e.Button == MouseButtons.Left) {
                Cursor = Cursors.Hand;

                ThumbCtl tc = (ThumbCtl)sender;

                Point pt = flpth.PointToClient(tc.PointToScreen(e.Location));
                Control p = flpth.GetChildAtPoint(pt);
                if (p != null && p != tc) {
                    int iFrm = flpth.Controls.IndexOf(tc);
                    int iTo = flpth.Controls.IndexOf((Control)p);
                    flpth.Controls.SetChildIndex(tc, iTo);
                    pic.MoveTo(iFrm, iTo);
                    Modified = true;
                }
            }
        }

        void tc_MouseDown(object sender, MouseEventArgs e) {
            if (e.Button == MouseButtons.Left) {
                int i = flpth.Controls.IndexOf((Control)sender);
                if (i < 0) return;

                SelPG(i);
            }
        }

        int PGi = -1; // -1 if no page seled

        ThumbCtl GetThc(int i) {
            return flpth.Controls[i] as ThumbCtl;
        }

        private void SelPG(int i) {
            PGi = Math.Min(cntPG - 1, i);

            for (int x = 0; x < cntPG; x++) {
                GetThc(x).Selected = (PGi == x);
            }
        }

        private void CloseTIFF() {
            if (pic != null) {
                pic.Dispose();
                pic = null;
            }
            PGi = -1;
            flpth.Controls.Clear();
            fpopen = null;
            isReadOnly = false;
            UpdateTitle();
            Modified = false;
        }

        bool isModified = false;

        public bool Modified { get { return isModified; } set { isModified = value; } }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>true=続行してください, false=中止</returns>
        bool QuerySave() {
            if (!Modified)
                return true;
            switch (MessageBox.Show(this, "変更されています。保存しますか。", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation)) {
                case DialogResult.Yes:
                    if (DoSave(false))
                        return true;
                    return false;
                case DialogResult.No:
                    return true;
                default:
                case DialogResult.Cancel:
                    return false;
            }
        }

        private void bNew_Click(object sender, EventArgs e) {
            NewTIFF();
        }

        PIC1 GetEditablePic(int i) {
            ThumbCtl tc = GetThc(i);
            if (tc == null) return null;
            return tc.PGImage;
        }

        PIC1 GetReadonlyPic(int i) {
            ThumbCtl tc = GetThc(i);
            if (tc == null) return null;
            return tc.PGImage;
        }

        private void bTurnL90_Click(object sender, EventArgs e) {
            if (PGi < 0) return;
            PIC1 pic = GetEditablePic(PGi);
            pic.RotateL90();
            Modified = true;
            UpdateThumb(PGi);
        }

        private void bTurnR90_Click(object sender, EventArgs e) {
            if (PGi < 0) return;
            PIC1 pic = GetEditablePic(PGi);
            pic.RotateR90();
            Modified = true;
            UpdateThumb(PGi);
        }

        Size sizeThumb = new Size(200, 200);

        private void UpdateThumb(int i) {
            ThumbCtl tc = flpth.Controls[i] as ThumbCtl;
            if (tc != null) {
                PIC1 pic = tc.PGImage;
                Debug.Assert(pic != null);
                tc.Thumbnail = CreateThumb(pic);
            }
        }

        String title;

        String DocName {
            get {
                if (fpopen == null)
                    return "無名.tif";
                return Path.GetFileName(fpopen);
            }
        }

        private void JForm_Load(object sender, EventArgs e) {
            title = this.Text;
            UpdateTitle();

            if (fpWantOpen != null) {
                LoadTIFF(fpWantOpen);
            }
            else {
                NewTIFF();
            }
        }

        private void NewTIFF() {
            if (QuerySave()) {
                CloseTIFF();

                pic = new PICS(Path.GetTempFileName());
            }
        }

        private void bMspaint_Click(object sender, EventArgs e) {
            if (PGi < 0) return;
            ThumbCtl tc = GetThc(PGi);
            if (tc == null) return;
            PIC1 pic = tc.PGImage;
            String fp = Path.GetTempFileName() + ".tif";
            pic.SaveTIF(fp);
            while (true) {
                Process p = Process.Start("mspaint.exe", " \"" + fp + "\"");

                using (TryImportForm form = new TryImportForm()) {
                    DialogResult res;
                    switch (res = form.ShowDialog()) {
                        case DialogResult.OK:
                        case DialogResult.Yes:
                            try {
                                using (WIP wip = WIP.Show(this, "取り込みしています...")) {
                                    FIBITMAP newpic = FreeImage.Load(FreeImage.GetFIFFromFilename(fp), fp, FREE_IMAGE_LOAD_FLAGS.DEFAULT);
                                    try {
                                        if (res == DialogResult.OK) {
                                            tc.PGImage.Replace(newpic);
                                            Modified = true;
                                            UpdateThumb(PGi);
                                        }
                                        else {
                                            AddPG(newpic);
                                            Modified = true;
                                            UpdateThumb(PGi);
                                        }
                                    }
                                    finally {
                                        FreeImage.Unload(newpic);
                                    }
                                }
                                return;
                            }
                            catch (Exception) {
                                MessageBox.Show(this, "取り込みに、失敗しました。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                            }
                            break;
                        case DialogResult.Cancel:
                            return;
                        default:
                        case DialogResult.Retry:
                            continue;
                    }
                }
            }
        }

        private void bDel_Click(object sender, EventArgs e) {
            if (PGi < 0) return;
            if (MessageBox.Show(this, "ページを削除します。\n\nよろしいですか。", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) != DialogResult.Yes)
                return;
            int i = PGi;
            pic.DeletePage(i);
            flpth.Controls[i].Dispose();
            Modified = true;
            SelPG(i);
        }

        private void bSend_Click(object sender, EventArgs e) {
            if (PGi < 0) return;
            PIC1 pic = GetReadonlyPic(PGi);
            if (pic == null) return;

            String fp = Path.GetTempFileName() + ".tif";
            pic.SaveTIF(fp);

            Process.Start(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MAPISendMailSa.exe"), " \"" + fp + "\"");
        }

        private void JForm_FormClosing(object sender, FormClosingEventArgs e) {
            if (Modified) {
                if (!QuerySave()) {
                    e.Cancel = true;
                    return;
                }
            }
        }

        private bool DoSave(bool forceSaveas) {
            try {
                bool wantToCancel = false;
                if (forceSaveas || fpopen == null || MaybeSaveas(fpopen, out wantToCancel)) {
                    if (wantToCancel)
                        return false;
                    sfdtif.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), DocName);
                    if (sfdtif.ShowDialog(this) != DialogResult.OK)
                        return false;
                    if (!ConfirmTagsDisposal(sfdtif.FileName))
                        return false;

                    DoSaveTo(sfdtif.FileName);
                    Modified = false;
                    isReadOnly = false;
                    fpopen = sfdtif.FileName;
                    UpdateTitle();
                    return true;
                }
                else {
                    if (!ConfirmTagsDisposal(fpopen))
                        return false;

                    DoSaveTo(fpopen);
                    Modified = false;
                    isReadOnly = false;
                    UpdateTitle();
                    return true;
                }
            }
            catch (SaveFailedException) {
                MessageBox.Show(this, "保存に失敗しました。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return false;
            }
        }

        private bool ConfirmTagsDisposal(string fp) {
            if (!File.Exists(fp))
                return true;
            try {
                SortedDictionary<string, string> dict = new SortedDictionary<string, string>();
                using (FileStream fs = File.OpenRead(fp)) {
                    Reader reader = new Reader(fs);
                    Entry[] al;
                    while (null != (al = reader.Read())) {
                        foreach (Entry e in al) {
                            // from http://www.awaresystems.be/imaging/tiff/tifftags/private.html
                            switch (e.tag) {
                                case 0x8649: dict["Adobe Photoshop"] = null; break;
                                case 0x935C: dict["Adobe Photoshop"] = null; break;
                                case 0xC612: dict["DNG"] = null; break;
                                case 0xC613: dict["DNG"] = null; break;
                                case 0xC614: dict["DNG"] = null; break;
                                case 0xC615: dict["DNG"] = null; break;
                                case 0xC616: dict["DNG"] = null; break;
                                case 0xC617: dict["DNG"] = null; break;
                                case 0xC618: dict["DNG"] = null; break;
                                case 0xC619: dict["DNG"] = null; break;
                                case 0xC61A: dict["DNG"] = null; break;
                                case 0xC61B: dict["DNG"] = null; break;
                                case 0xC61C: dict["DNG"] = null; break;
                                case 0xC61D: dict["DNG"] = null; break;
                                case 0xC61E: dict["DNG"] = null; break;
                                case 0xC61F: dict["DNG"] = null; break;
                                case 0xC620: dict["DNG"] = null; break;
                                case 0xC621: dict["DNG"] = null; break;
                                case 0xC622: dict["DNG"] = null; break;
                                case 0xC623: dict["DNG"] = null; break;
                                case 0xC624: dict["DNG"] = null; break;
                                case 0xC625: dict["DNG"] = null; break;
                                case 0xC626: dict["DNG"] = null; break;
                                case 0xC627: dict["DNG"] = null; break;
                                case 0xC628: dict["DNG"] = null; break;
                                case 0xC629: dict["DNG"] = null; break;
                                case 0xC62A: dict["DNG"] = null; break;
                                case 0xC62B: dict["DNG"] = null; break;
                                case 0xC62C: dict["DNG"] = null; break;
                                case 0xC62D: dict["DNG"] = null; break;
                                case 0xC62E: dict["DNG"] = null; break;
                                case 0xC62F: dict["DNG"] = null; break;
                                case 0xC630: dict["DNG"] = null; break;
                                case 0xC631: dict["DNG"] = null; break;
                                case 0xC632: dict["DNG"] = null; break;
                                case 0xC634: dict["DNG"] = null; break;
                                case 0xC635: dict["DNG"] = null; break;
                                case 0xC65A: dict["DNG"] = null; break;
                                case 0xC65B: dict["DNG"] = null; break;
                                case 0xC65C: dict["DNG"] = null; break;
                                case 0xA480: dict["GDAL"] = null; break;
                                case 0xA481: dict["GDAL"] = null; break;
                                case 0x830E: dict["GeoTIFF"] = null; break;
                                case 0x8480: dict["GeoTIFF"] = null; break;
                                case 0x8482: dict["GeoTIFF"] = null; break;
                                case 0x85D8: dict["GeoTIFF"] = null; break;
                                case 0x87AF: dict["GeoTIFF"] = null; break;
                                case 0x87B0: dict["GeoTIFF"] = null; break;
                                case 0x87B1: dict["GeoTIFF"] = null; break;
                                case 0x885C: dict["HylaFAX"] = null; break;
                                case 0x885D: dict["HylaFAX"] = null; break;
                                case 0x885E: dict["HylaFAX"] = null; break;
                                case 0x80A4: dict["Imaging"] = null; break;
                                case 0x847E: dict["Intergraph Application"] = null; break;
                                case 0x847F: dict["Intergraph Application"] = null; break;
                                case 0x83BB: dict["IPTC"] = null; break;
                                case 0x82A5: dict["Molecular Dynamics GEL"] = null; break;
                                case 0x82A6: dict["Molecular Dynamics GEL"] = null; break;
                                case 0x82A7: dict["Molecular Dynamics GEL"] = null; break;
                                case 0x82A8: dict["Molecular Dynamics GEL"] = null; break;
                                case 0x82A9: dict["Molecular Dynamics GEL"] = null; break;
                                case 0x82AA: dict["Molecular Dynamics GEL"] = null; break;
                                case 0x82AB: dict["Molecular Dynamics GEL"] = null; break;
                                case 0x82AC: dict["Molecular Dynamics GEL"] = null; break;
                                case 0xC427: dict["Oce scanning"] = null; break;
                                case 0xC428: dict["Oce scanning"] = null; break;
                                case 0xC429: dict["Oce scanning"] = null; break;
                                case 0xC42A: dict["Oce scanning"] = null; break;
                                case 0xC660: dict["Sketchbook Pro"] = null; break;
                                case 0x8769: dict["不明"] = null; break;
                                case 0x8825: dict["不明"] = null; break;
                                case 0x8773: dict["不明"] = null; break;
                                case 0xA005: dict["不明"] = null; break;
                            }
                        }
                    }
                }

                String s = "";
                foreach (String kw in dict.Keys) s += "- " + kw + "\n";

                if (dict.Count != 0) {
                    if (MessageBox.Show(this, "上書き保存しますと、他のソフトウェアで追加した情報を失う可能性が有ります。情報：\n\n" + s + "\n" + "それでも、続行しますか。", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) != DialogResult.Yes)
                        return false;
                }
                return true;
            }
            catch (Exception) {
                return true;
            }
        }

        private bool MaybeSaveas(string fpopen, out bool wantToCancel) {
            if (fpopen == this.fpopen) {
                if (isReadOnly) {

                }
                else {
                    wantToCancel = false;
                    return false;
                }
            }
            if (Testaa(fpopen) != AllowedAccess.ReadWrite) {
                switch (MessageBox.Show(this, "開いたファイルは、読み取り専用になっています。\n\n上書き保存できません。\n\n保存するには、別のファイルに保存してください。\n\n別のファイルに保存しますか。", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation)) {
                    case DialogResult.Yes:
                        wantToCancel = false;
                        return true;
                    default:
                    case DialogResult.No:
                        wantToCancel = true;
                        return true;
                }
            }
            wantToCancel = false;
            return false;
        }

        ImageCodecInfo TIF {
            get {
                foreach (ImageCodecInfo ici in ImageCodecInfo.GetImageDecoders()) {
                    if (ici.MimeType == "image/tiff")
                        return ici;
                }
                return null;
            }
        }

        private void DoSaveTo(String fpsave) {
            using (WIP wip = WIP.Show(this, "保存しています...")) {
                pic.SaveTo(fpsave);
            }
        }

        private void bSave_Click(object sender, EventArgs e) {
            DoSave((sender == bSaveas));
        }

        private void bAbout_Click(object sender, EventArgs e) {
            using (AboutBox form = new AboutBox()) {
                form.ShowDialog(this);
            }
        }

        private void JForm_DragEnter(object sender, DragEventArgs e) {
            e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
        }

        private void JForm_DragDrop(object sender, DragEventArgs e) {
            String[] alfp = e.Data.GetData(DataFormats.FileDrop) as String[];
            if (alfp != null && alfp.Length != 0) {
                using (HowToAddForm form = new HowToAddForm()) {
                    DialogResult res;
                    switch (res = form.ShowDialog(this)) {
                        case DialogResult.OK: // 開く
                            if (!QuerySave())
                                return;
                            LoadTIFF(alfp[0]);
                            break;

                        case DialogResult.Retry: // 追加
                            List<string> alerrfp = new List<string>();
                            using (WIP wip = WIP.Show(this, "追加しています...")) {
                                foreach (String fp in alfp) {
                                    try {
                                        FIMULTIBITMAP pic = FreeImage.OpenMultiBitmap(FreeImage.GetFIFFromFilename(fp), fp, false, true, false, FreeImageAPI.FREE_IMAGE_LOAD_FLAGS.DEFAULT);
                                        try {
                                            int cx = FreeImage.GetPageCount(pic);

                                            for (int x = 0; x < cx; x++) {
                                                FIBITMAP dib = FreeImage.LockPage(pic, x);
                                                AddPG(dib);
                                                Modified = true;
                                                FreeImage.UnlockPage(pic, dib, false);
                                            }
                                        }
                                        finally {
                                            FreeImage.CloseMultiBitmap(pic, FREE_IMAGE_SAVE_FLAGS.DEFAULT);
                                        }
                                    }
                                    catch (Exception) {
                                        alerrfp.Add(fp);
                                    }
                                }
                            }
                            if (alerrfp.Count != 0) {
                                MessageBox.Show(this, "次のファイルは、取り込みできませんでした:\n\n" + string.Join("\n", alerrfp.ToArray()), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                            }
                            break;

                        case DialogResult.Cancel: // キャンセル
                        default:
                            break;
                    }
                }
            }
        }
    }

    public class SaveFailedException : Exception {
        public SaveFailedException(Exception inside)
            : base("保存に失敗しました", inside) {

        }
    }
}